In [1]:
%pip install transliterate
Requirement already satisfied: transliterate in /home/olena/myvenv/lib/python3.12/site-packages (1.10.2) Requirement already satisfied: six>=1.1.0 in /home/olena/myvenv/lib/python3.12/site-packages (from transliterate) (1.17.0) Note: you may need to restart the kernel to use updated packages.
In [2]:
%pip install rapidfuzz
Requirement already satisfied: rapidfuzz in /home/olena/myvenv/lib/python3.12/site-packages (3.13.0) Note: you may need to restart the kernel to use updated packages.
In [3]:
import pandas as pd
import numpy as np
import altair as alt
import matplotlib.pyplot as plt
from transliterate import translit
from rapidfuzz import process, fuzz
In [4]:
df2_1_lyst = pd.read_csv('2.1_Дунаєць.xlsx - База листов.csv')
df2_1_people = pd.read_csv('2.1_Дунаєць.xlsx - База людей.csv')
df2_2_base = pd.read_csv('2.2_Дунаєць_1778.xlsx - База.csv')
In [5]:
#Статеве співвідношення різних вікових груп, як змінилося за сто років
#1 датасет
#Очистка даних
#Вік
#Спочатку перекладаємо англіською колонку "Возраст" і перевіряємо, чи всі значення числові
df2_1_people = df2_1_people.rename(columns={'Возраст': 'Age'})
mask = df2_1_people['Age'].astype(str).str.contains(r'[a-zA-Z]', na=False)
df2_1_people[mask]
Out[5]:
| Кто заполнял базу | ID строки в базе | List ID | Link | ID Домохозяйствао | ID жилец | ФИО | Пол | Глава хозяйства и глава семьи | Age | ... | Здесь ли обыкновенно проживает | Отметка об отсуствии | Вероисповедание | Родной язык | Умеет ли читать | Обучение | Профессия главное | Профессия вспомогательное | Положение по воинской повинности | Примітки | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 42 | Podhorna | 43.0 | F.132.Op.1.Spr.1343. Арк.13-14 | https://www.familysearch.org/ark:/61903/3:1:3Q... | 6.0 | 11.0 | Lukash Georgij Mikhajlov | m | grandson/son | 2 months | ... | 1 | NaN | orthodox | ukrm | 0.0 | NaN | with father | NaN | NaN | NaN |
| 89 | Podhorna | 90.0 | F.132.Op.1.Spr.1343. Арк.33-34 | https://www.familysearch.org/ark:/61903/3:1:3Q... | 16.0 | 5.0 | Sachkova Mariya Ivanova | f | daughter | 6 months | ... | 1 | NaN | orthodox | ukrm | 0.0 | NaN | with parents | NaN | NaN | NaN |
| 141 | Podhorna | 142.0 | F.132.Op.1.Spr.1343. Арк.53-54 | https://www.familysearch.org/ark:/61903/3:1:3Q... | 26.0 | 5.0 | Davidenko Daniil Vasiliev | m | son | 6 months | ... | 1 | NaN | orthodox | ukrm | 0.0 | NaN | with father | NaN | NaN | NaN |
| 165 | Podhorna | 166.0 | F.132.Op.1.Spr.1343. Арк.61-62 | https://www.familysearch.org/ark:/61903/3:1:3Q... | 30.0 | 6.0 | Sapozhnikov Andrej Zakhariev | m | grandson/son | 4 months | ... | 1 | NaN | orthodox | ukrm | 0.0 | NaN | with father | NaN | NaN | NaN |
| 189 | Podhorna | 190.0 | F.132.Op.1.Spr.1343. Арк.69-70 | https://www.familysearch.org/ark:/61903/3:1:3Q... | 34.0 | 4.0 | Kontribuc Ivan Savvin | m | son | 2 months | ... | 1 | NaN | orthodox | ukrm | 0.0 | NaN | with father | NaN | NaN | NaN |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 1527 | Podhorna | 1528.0 | F.132.Op.1.Spr.1343. Арк.544-545 | https://www.familysearch.org/ark:/61903/3:1:3Q... | 258.0 | 6.0 | Belyavskaya Varvara Ivanova | f | daughter | 2 months | ... | 1 | NaN | orthodox | ukrm | 0.0 | NaN | with parents | NaN | NaN | NaN |
| 1548 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 1549 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 1550 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 1551 | * - В оригінальному джерелі ячейка "мова" була... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
69 rows × 25 columns
In [6]:
#Приходимо до висновку, що деякий вік записаний не в роках, а у деяких клітинках NaN значення, що нам не підходить
#Оскільки статистично нам немає різниці, скільки конкретно місяців/тижнів/днів дитині, записуємо як 0 років
df2_1_people = df2_1_people.replace(to_replace=r'.*month.*', value=0, regex=True)
df2_1_people = df2_1_people.replace(to_replace=r'.*days.*', value=0, regex=True)
df2_1_people = df2_1_people.replace(to_replace=r'.*weeks.*', value=0, regex=True)
#Останні 4 рядки не містять даних, їх видаляємо
df2_1_people = df2_1_people.iloc[:-4]
#Залишилось 3 рядка із невідомим віком - на жаль, в інших таблицях не зазначено інформації про вік, тому ці 3 випадки виключаємо
df2_1_people_with_age = df2_1_people.drop(index=[987, 988, 989])
In [7]:
df2_1_people_with_age['Age'] = pd.to_numeric(df2_1_people_with_age['Age'], errors='coerce').astype('float64')
In [8]:
df2_1_people_with_age.Age.describe()
Out[8]:
count 1545.000000 mean 25.423625 std 19.493821 min 0.000000 25% 9.000000 50% 23.000000 75% 39.000000 max 90.000000 Name: Age, dtype: float64
In [9]:
#дані чисті - вік що 0, що 90 не є аномальними, тому вони важливі для статистики
In [10]:
#Стать
#Перевірка
df2_1_people = df2_1_people.rename(columns={'Пол': 'Sex'})
df2_1_people[~df2_1_people['Sex'].isin(['f', 'm'])]
#Ті самі винятки, що і були з віком
df2_1_lyst.columns
Out[10]:
Index(['Кто заполнял базу', 'List ID', 'Link', 'ID Страница',
'ID Домохозяйствао', 'Губерния', 'Уезд', 'Волость ', 'Село/деревня',
'Переписной участок', 'Счетный участок', 'Стан или полицейский участок',
'Хозяин', 'Сколько жилых строений', 'Строение построено', 'Чем крыто',
'Всего наличного мужеского населения',
'Всего наличнаго женского населения', 'Постоянно живущаго М',
'Постоянно живущаго Ж', 'Некрестьянсокго сословия М',
'Некрестьянского сословия Ж', 'Приписанного здесь М',
'Приписанного здесь Ж', 'Подпись счетчика'],
dtype='object')
In [11]:
#У таблиці 2.1 База листов є дані про 'Всего наличнаго женского населения' і 'Всего наличного мужеского населения', що і використаємо для статистики
df_merged = pd.merge(df2_1_people, df2_1_lyst, on='ID Домохозяйствао')
df_merged[df_merged['Age'].isna()]
Out[11]:
| Кто заполнял базу_x | ID строки в базе | List ID_x | Link_x | ID Домохозяйствао | ID жилец | ФИО | Sex | Глава хозяйства и глава семьи | Age | ... | Чем крыто | Всего наличного мужеского населения | Всего наличнаго женского населения | Постоянно живущаго М | Постоянно живущаго Ж | Некрестьянсокго сословия М | Некрестьянского сословия Ж | Приписанного здесь М | Приписанного здесь Ж | Подпись счетчика | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 987 | Podhorna | 988.0 | F.132.Op.1.Spr.1343. Арк.355-356 | NaN | 169.0 | NaN | NaN | NaN | NaN | NaN | ... | straw | 1.0 | 2.0 | 1.0 | 2.0 | 0.0 | 0.0 | 1.0 | 2.0 | Ivan Belyavskij |
| 988 | Podhorna | 989.0 | F.132.Op.1.Spr.1343. Арк.355-356 | NaN | 169.0 | NaN | NaN | NaN | NaN | NaN | ... | straw | 1.0 | 2.0 | 1.0 | 2.0 | 0.0 | 0.0 | 1.0 | 2.0 | Ivan Belyavskij |
| 989 | Podhorna | 990.0 | F.132.Op.1.Spr.1343. Арк.355-356 | NaN | 169.0 | NaN | NaN | NaN | NaN | NaN | ... | straw | 1.0 | 2.0 | 1.0 | 2.0 | 0.0 | 0.0 | 1.0 | 2.0 | Ivan Belyavskij |
3 rows × 49 columns
In [12]:
#Усі ці 3 винятки з одного господарства, в якому 1 чоловік і 2 жінки
df2_1_people.loc[987, 'Sex'] = 'm'
df2_1_people.loc[988, 'Sex'] = 'f'
df2_1_people.loc[989, 'Sex'] = 'f'
#замінили NaN значеннями вручну
#варто зазначити, що для статистики початково не брались стовпці із господарствами тому, що NaN значення у них значно переважали той, з яким я працювала
df2_1_people[~df2_1_people['Sex'].isin(['f', 'm'])]
Out[12]:
| Кто заполнял базу | ID строки в базе | List ID | Link | ID Домохозяйствао | ID жилец | ФИО | Sex | Глава хозяйства и глава семьи | Age | ... | Здесь ли обыкновенно проживает | Отметка об отсуствии | Вероисповедание | Родной язык | Умеет ли читать | Обучение | Профессия главное | Профессия вспомогательное | Положение по воинской повинности | Примітки |
|---|
0 rows × 25 columns
In [13]:
#2 датасет
df2_2_base_processed = df2_2_base.copy()
df2_2_base_processed.columns = df2_2_base_processed.iloc[2]
df2_2_base_processed = df2_2_base_processed.iloc[3:]
df2_2_base_processed = df2_2_base_processed.reset_index(drop=True)
df2_2_base_processed.columns.values[0] = 'Аркуш'
df2_2_base_processed.columns.values[4] = 'Чоловіки наскрізне'
df2_2_base_processed.columns.values[5] = 'Жінки наскрізне'
df2_2_base_processed.columns.values[6] = 'Чоловіки джерело'
df2_2_base_processed.columns.values[7] = 'Жінки джерело'
#виправлено назви стовпців
df2_2_base_processed
Out[13]:
| 2 | Аркуш | ID наскрізна | ID домогосподарство | Двори джерело | Чоловіки наскрізне | Жінки наскрізне | Чоловіки джерело | Жінки джерело | Імʼя | По-батькові | Прізвище | Родиний статус | Категорія | Клас | Соціальний статус | Вік | Був на сповіді | Не був | Не був за малолітством |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 445 | 1 | 1 | 1 | 1 | NaN | 1 | NaN | Федор | Кондратьев | Лукашевич | господар | nuclear | 3b | духовные | 44 | х | NaN | NaN |
| 1 | NaN | 2 | NaN | NaN | NaN | 1 | NaN | 1 | Варвара | Симева | NaN | жена | NaN | NaN | духовные | 35 | х | NaN | NaN |
| 2 | NaN | 3 | NaN | NaN | NaN | 2 | NaN | 2 | Анна | NaN | NaN | дочь | NaN | NaN | духовные | 17 | х | NaN | NaN |
| 3 | NaN | 4 | NaN | NaN | 2 | NaN | 2 | NaN | Тимофей | NaN | NaN | сын | NaN | NaN | духовные | 15 | х | NaN | NaN |
| 4 | NaN | 5 | NaN | NaN | 2 | NaN | 2 | NaN | Пантелеймон | NaN | NaN | сын | NaN | NaN | духовные | 14 | х | NaN | NaN |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 1592 | NaN | 1593 | 104 | 12 | 824 | NaN | 236 | NaN | Леонтий | Афанасиев | NaN | двоюродный брат Александра | nuclear | 3a | бездворные | 25 | х | NaN | NaN |
| 1593 | NaN | 1594 | NaN | NaN | NaN | 770 | NaN | 222 | Мотрона | Михайлова | NaN | жена его | NaN | NaN | бездворные | 22 | х | NaN | NaN |
| 1594 | NaN | 1595 | 105 | 13 | 825 | NaN | 237 | NaN | Герасим | Фомик | Мерник | господар | nuclear | 3b | бездворные | 43 | х | NaN | NaN |
| 1595 | NaN | 1596 | NaN | NaN | NaN | 771 | NaN | 223 | Агафия | Максимова | NaN | жена его | NaN | NaN | бездворные | 42 | х | NaN | NaN |
| 1596 | NaN | 1597 | NaN | NaN | 826 | NaN | 238 | NaN | Лукьян | NaN | NaN | NaN | NaN | NaN | бездворные | 14 | х | NaN | NaN |
1597 rows × 19 columns
In [14]:
df2_2_base_processed['Age'] = df2_2_base_processed['Вік']
mask = df2_2_base_processed['Age'].astype(str).str.contains(r'[a-zA-Z]', na=False)
df2_2_base_processed[mask]
Out[14]:
| 2 | Аркуш | ID наскрізна | ID домогосподарство | Двори джерело | Чоловіки наскрізне | Жінки наскрізне | Чоловіки джерело | Жінки джерело | Імʼя | По-батькові | Прізвище | Родиний статус | Категорія | Клас | Соціальний статус | Вік | Був на сповіді | Не був | Не був за малолітством | Age |
|---|
In [15]:
df2_2_base_processed['Age'] = pd.to_numeric(df2_2_base_processed['Age'], errors='coerce').astype('float64')
df2_2_base_processed.Age.describe()
Out[15]:
count 1597.000000 mean 23.529743 std 16.608718 min 1.000000 25% 9.000000 50% 21.000000 75% 35.000000 max 84.000000 Name: Age, dtype: float64
In [16]:
#знову немає аномалій
In [17]:
#оскільки наскрізні дані і дані джерела занадто сильно різняться, стать краще опрацювати через родинний статус
df2_2_base_processed['Родиний статус'].unique()
Out[17]:
array(['господар', 'жена', 'дочь', ' сын', 'сын', 'дячок',
'брат Феодосия', 'жена Савы', 'жена Григория', 'жена Кирила',
'господар вдов', 'жена Алексеея', 'сын Алексея', 'дочь Алексея',
'жена Антона', 'сын Антона', 'дочь Антона', 'жена Андрея',
'сын Андрея', 'дочь Андрея', 'жена Стефана', 'дочь стефана',
'племянник Кузьмы вдов', 'сын Герасима', 'жена Ивана',
'жена Никиты', 'сын Никиты', 'жена Максима', 'племянник Герасима',
'племянница Герасима', 'невестка Кузьмы вдова', 'сын Федоры',
'жена Павла', 'сын Павла', 'жена Леонтия', 'племянник Феодосии',
'жена Герасима', 'дочь Герасима', 'племянник Кузьмы',
'Федора жена', 'дочь Федора', 'сын Федора', 'жена Мирона',
'сын Мирона', 'жена Ивана Мойсеева', 'дочь их', 'сын их',
'племянник Ивана', 'жена его', 'сын его', 'жена Василия',
'брат Федора', 'жена Терентия', 'дядина Федора удова',
'сын Зиновии', 'дочь Зиновии', 'племяннкк Фомы', 'доч их',
'брат Конона', 'зать Мирона', 'невестка Конона вдова',
'сын Екатерины', 'дочь Екатерины', 'дядя Конона', 'жена Бориса',
'жена Романа', 'дочь Романа', 'жена Гаврила', 'дочь Гаврила',
'жена Якова', 'брат Никиты', 'жена Федора', 'жена Тараса',
'брат Ивана', 'жена Ефима', 'жена Никифора', 'жена Онисима',
'зять Прокопа', 'брат Прокопа', 'невестка Прокопа вдова', 'сын ее',
'дочь ее', 'жена Филипа', 'жена Родиона', 'брат Захария',
'жена Михаила', 'брат двоюродный Захария', 'жена Сазона', nan,
'дядина Захария вдова', 'жена Трофима', 'жена Николая',
'жена Петрова', 'жена Тимофея', 'брат Каленика',
'дядя Каленика вдов', 'племянник Каленика',
'племянник Каленика вдов', 'жена Прокопа', 'зять Конона',
'племянник Конона', 'двоюродный брат Федора', 'жена Емельяна',
'невестка Никона вдова', 'зять Ефимия', 'племянник Никона',
'жена Кондрата', 'жена Григоря', 'жена Дионисия', 'брат Харитона',
'зять Леонтия', 'зять Алексея', 'племянник Артема',
'двоюродный брат Артема', 'братов племянник', 'зять Гаврила',
'невестка братов удова', 'жена Марка', 'зять Зиновии',
'двоюродный брат Зиновии', 'брат Кондрата', 'племянник братов',
'жена Дмитрия', 'жена Климентия', 'племянник Петра',
'жена Ермолая', 'племянник Фтеодора', 'жена Петра', 'жена Артема',
'зять Артема', 'племянник Елисея', 'племянница Елисея',
'жена Евтихия', 'сын их вдов', 'дочь Еремея', 'жена Корнилия',
'брат Иоакима', 'двоюродный брат Кореня', 'жена Якима', '?',
'брат Терентия', 'двоюродный брат Демьяна', 'брат Игната',
'жена Сидора', 'двоюродный брат Ивана', 'дочь его', 'зять их',
'жена Уласа', 'зять Терентия', 'зать Устины', 'жена Матвея',
'невестка Филона вдова', 'невестка Евсевия вдова',
'племянник Евсея', 'племянник Максима', 'жена Иосифа',
'жена Гордея', 'двоюродный брат Стефана', 'брат Якова',
'господар вдова', 'жена Потапа', 'племянник Агафии', 'жена Конона',
'жена Фомы', 'невестка Акилины вдова', 'сын ее вдов',
'жена Семена', 'дочь Якова', 'невестка Ивана вдова', 'гоподар',
'двоюродный брат Лазара', 'брат Антона', 'племянник Антона',
'жена Нестора', 'племянница Антона вдова', 'брат Тараса',
'двоюродный дядя Тараса', 'жена Клима', 'брат Логвина',
'племянник Логвина', 'жена Марко', 'брат Кузьмы',
'племянник Клима', 'жена Сергея', 'жена Алексея', 'зять Федора',
'зять Семена вдов', 'двоюродный дядя Семена',
'племянник Параскеви', 'брат Леонтия', 'сестра Леонтия',
'брат Василия', 'двоюродный барт Василия', 'плеянник Федора',
'зять Кирила', 'жена Никона', 'брат Емельяна', 'племянник Семена',
'племянница Семена', 'онук Родиона', 'племянник Родиона',
'племянник Афанасия', 'двоюродный брат Афанасия',
'дядина Конона вдова', 'жена Лаврентия', 'двоюродный брат Конона',
'двоюродный брат Василия', 'племянник Наталии вдов',
'невестка Кирила вдова', 'жена Кузьмы', 'двоюродный брат Кирила',
'мать Андрея вдова', 'племянниа Андрея вдова', 'племянник Андрея',
'двоюродный брат Андрея', 'дядя двоюродный Андрея',
'двоюродный брат Федора вдов',
'двоюродный брат Федора и родной брат Касьяна', 'племянник Никиты',
'брат Савы', 'дядина Савы вдова', 'жена Давида', 'зять Любови',
'жена Мойсея', 'племянник Павла', 'племянница Павла',
'дядя двоюродный Павла', 'невестка Дмитрия вдова', 'девушка',
'удова ключница', 'староста', 'повар', 'кухарка вдова',
'иконописец', 'кузнец', 'охотник', 'ученик', 'сапожник',
'садовник', 'брат Иосифа', 'дворник', 'гончар', 'конюх', 'кучер',
'брат Трофима', 'сестра Трофима', 'дядина Трофима удова',
'племянник Григория', 'невестка Василия удова',
'невестка Григория удова', 'жена Касьяна', 'племянник Иосифа',
'двоюродный брат Никиты удов ', 'брат Марка', 'теща Ефима удова',
'теща Симеона удова', 'двоюродный брат Симеона', 'зять Василия',
'зять Романа', 'зать Михайла', 'жена Симеона',
'двоюродный брат Савы', 'зять Павла', 'швагер Федора',
'сестра его', 'дядина Павла', 'жена Гордиева', 'господар удов',
'брат Григория', 'жена Алексеяя', 'жена Захария', 'сосед Ивана',
'імовірно голова іншої родини', 'брат Еремея',
'мать Василия удова', 'дядина Василия удова', 'жена Федова',
'брат Симеона', 'невестка Симеона', 'жена Карпа', 'брат Михайла',
'племянник Михайла', 'швагер Константина', 'брат Никифора',
'жена Евсевия', 'двоюродный брат Харитона', 'гоподар удов',
'жена Елельяна', 'теща Василия удова', 'господар удова',
'жена Гавриила', 'жена Александра', 'двоюродный брат Александра'],
dtype=object)
In [18]:
def guess_sex(status):
if pd.isna(status):
return pd.NA
status = status.lower().strip()
male_keywords = [
'господар', 'сын', 'брат', 'дядя', 'зять', 'швагер', 'гончар', 'староста', 'кузнец',
'сосед', 'кучер', 'конюх', 'садовник', 'дворник', 'охотник', 'племянник', 'двоюродный',
'братов', 'онук', 'удов', 'гоподар', 'дядина', 'братов племянник', 'дячок', 'зать',
'плеянник', 'ученик', 'иконописец', 'голова', 'племяннкк', 'повар', 'сапожник'
]
female_keywords = [
'жена', 'дочь', 'вдова', 'невестка', 'мать', 'сестра', 'кухарка', 'девушка', 'теща', 'сестра',
'вдова', 'племянница', 'доч',
]
if any(k in status for k in male_keywords):
return 'm'
elif any(k in status for k in female_keywords):
return 'f'
else:
return pd.NA
df2_2_base_processed['Sex'] = df2_2_base_processed['Родиний статус'].apply(guess_sex)
df2_2_base_processed[df2_2_base_processed['Sex'].isna()]
Out[18]:
| 2 | Аркуш | ID наскрізна | ID домогосподарство | Двори джерело | Чоловіки наскрізне | Жінки наскрізне | Чоловіки джерело | Жінки джерело | Імʼя | По-батькові | ... | Родиний статус | Категорія | Клас | Соціальний статус | Вік | Був на сповіді | Не був | Не був за малолітством | Age | Sex |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 248 | NaN | 249 | NaN | NaN | 126 | NaN | 126 | NaN | Николай | Григорьев | ... | NaN | NaN | NaN | военные | 39 | х | NaN | NaN | 39.0 | <NA> |
| 530 | NaN | 531 | NaN | NaN | 267 | NaN | 267 | NaN | Андрей | Стефанов | ... | ? | NaN | NaN | военные | 30 | х | NaN | NaN | 30.0 | <NA> |
| 607 | NaN | 608 | NaN | NaN | NaN | 302 | NaN | 302 | Устина | Мойсеева | ... | NaN | NaN | NaN | военные | 49 | х | NaN | NaN | 49.0 | <NA> |
| 1596 | NaN | 1597 | NaN | NaN | 826 | NaN | 238 | NaN | Лукьян | NaN | ... | NaN | NaN | NaN | бездворные | 14 | х | NaN | NaN | 14.0 | <NA> |
4 rows × 21 columns
In [19]:
#пусті клітинки які залишились заповнюємо вручну орієнтуючить на імена
df2_2_base_processed.loc[248, 'Sex'] = 'm'
df2_2_base_processed.loc[530, 'Sex'] = 'm'
df2_2_base_processed.loc[607, 'Sex'] = 'f'
df2_2_base_processed.loc[1596, 'Sex'] = 'm'
df2_2_base_processed[df2_2_base_processed['Sex'].isna()]
Out[19]:
| 2 | Аркуш | ID наскрізна | ID домогосподарство | Двори джерело | Чоловіки наскрізне | Жінки наскрізне | Чоловіки джерело | Жінки джерело | Імʼя | По-батькові | ... | Родиний статус | Категорія | Клас | Соціальний статус | Вік | Був на сповіді | Не був | Не був за малолітством | Age | Sex |
|---|
0 rows × 21 columns
In [20]:
#найдоречнішим, на мою думку,є створити вікову піраміду із обома роками
df1 = df2_2_base_processed.copy()
df2 = pd.merge(df2_1_people_with_age[['Age']], df2_1_people[['Sex']],
left_index=True, right_index=True, how='inner')
def create_age_groups(age):
if age < 10:
return '0-9'
elif age < 20:
return '10-19'
elif age < 30:
return '20-29'
elif age < 40:
return '30-39'
elif age < 50:
return '40-49'
elif age < 60:
return '50-59'
elif age < 70:
return '60-69'
else:
return '70+'
def process_dataset(df):
df['age_group'] = df['Age'].apply(create_age_groups)
grouped = df.groupby(['age_group', 'Sex']).size().unstack(fill_value=0)
age_order = ['0-9', '10-19', '20-29', '30-39', '40-49', '50-59', '60-69', '70+']
grouped = grouped.reindex(age_order)
females = grouped.get('f', pd.Series(0, index=grouped.index))
males = grouped.get('m', pd.Series(0, index=grouped.index))
return females, males, age_order
females1, males1, age_order = process_dataset(df1)
females2, males2, age_order = process_dataset(df2)
fig, ax = plt.subplots(1, 1, figsize=(14, 10))
y_pos = np.arange(len(age_order))
bar_height = 0.35
# 1778 рік
ax.barh(y_pos - bar_height/2, -males1, height=bar_height,
label='Чоловіки 1778', color='#1f4e79', alpha=0.8)
ax.barh(y_pos - bar_height/2, females1, height=bar_height,
label='Жінки 1778', color='#a61e1e', alpha=0.8)
# 1897 рік
ax.barh(y_pos + bar_height/2, -males2, height=bar_height,
label='Чоловіки 1897', color='#4472C4', alpha=0.7)
ax.barh(y_pos + bar_height/2, females2, height=bar_height,
label='Жінки 1897', color='#E15759', alpha=0.7)
ax.set_yticks(y_pos)
ax.set_yticklabels(age_order)
ax.set_xlabel('Кількість населення', fontsize=12)
ax.set_ylabel('Вікові групи', fontsize=12)
ax.set_title('Порівняння статево-вікової структури населення 1778 та 1897 років',
fontsize=14, fontweight='bold', pad=20)
max_val = max(males1.max(), females1.max(), males2.max(), females2.max())
ax.set_xlim(-max_val * 1.2, max_val * 1.2)
#фіксимо негативні значення зліва
ticks = ax.get_xticks()
ax.set_xticklabels([abs(int(tick)) for tick in ticks])
ax.axvline(x=0, color='black', linewidth=0.8)
ax.legend(loc='upper right', fontsize=10)
min_threshold = max_val * 0.05
#числа на малих стовбцях погано зчитуються
for i, (m1, f1, m2, f2) in enumerate(zip(males1, females1, males2, females2)):
# 1778 рік
if m1 > min_threshold:
ax.text(-m1/2, i - bar_height/2, str(m1), ha='center', va='center',
fontweight='bold', fontsize=8, color='white')
if f1 > min_threshold:
ax.text(f1/2, i - bar_height/2, str(f1), ha='center', va='center',
fontweight='bold', fontsize=8, color='white')
# 1897 рік
if m2 > min_threshold:
ax.text(-m2/2, i + bar_height/2, str(m2), ha='center', va='center',
fontweight='bold', fontsize=8, color='white')
if f2 > min_threshold:
ax.text(f2/2, i + bar_height/2, str(f2), ha='center', va='center',
fontweight='bold', fontsize=8, color='white')
ax.grid(axis='x', alpha=0.3)
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
plt.tight_layout()
total_1778 = males1.sum() + females1.sum()
total_1897 = males2.sum() + females2.sum()
info_text = f"""Загальна кількість населення:
• 1778 рік: {total_1778:,} осіб
• 1897 рік: {total_1897:,} осіб
Розподіл за статтю:
1778 рік:
• Чоловіки: {males1.sum():,} ({(males1.sum() / total_1778 * 100):.1f}%)
• Жінки: {females1.sum():,} ({(females1.sum() / total_1778 * 100):.1f}%)
1897 рік:
• Чоловіки: {males2.sum():,} ({(males2.sum() / total_1897 * 100):.1f}%)
• Жінки: {females2.sum():,} ({(females2.sum() / total_1897 * 100):.1f}%)"""
ax = fig.gca()
ax.text(0.02, 0.98, info_text, transform=ax.transAxes, fontsize=9,
verticalalignment='top', horizontalalignment='left',
bbox=dict(boxstyle='round,pad=0.5', facecolor='lightgray', alpha=0.8))
plt.show()
/tmp/ipykernel_28230/2930493881.py:68: UserWarning: set_ticklabels() should only be used with a fixed number of ticks, i.e. after set_ticks() or using a FixedLocator. ax.set_xticklabels([abs(int(tick)) for tick in ticks])
In [21]:
'''
з цієї піраміди можна зробити такі висновки:
-серед населення 0-9 та 40+ років відносна кількість жінок зросла
-серед населення віком 10-19, 20-29 та 30-39 років відносність не зазнала суттєвих змін
-з роками суттєво збільшилась кількість людей 60+ обох статей
-кількість населення не зазнала великих змін, але відносна кількість жінок збільшилась
-серед населення 60+ досі переважають чоловіки
'''
Out[21]:
'\nз цієї піраміди можна зробити такі висновки: \n-серед населення 0-9 та 40+ років відносна кількість жінок зросла \n-серед населення віком 10-19, 20-29 та 30-39 років відносність не зазнала суттєвих змін\n-з роками суттєво збільшилась кількість людей 60+ обох статей\n-кількість населення не зазнала великих змін, але відносна кількість жінок збільшилась\n-серед населення 60+ досі переважають чоловіки\n'
In [22]:
#Структура і розміри родин; як це змінилось за сто років
#датасет 1
#розміри сім'ї
df2_1_lyst.rename(columns={'Всего наличного мужеского населения': 'Males count'}, inplace=True)
df2_1_lyst.rename(columns={'Всего наличнаго женского населения': 'Females count'}, inplace=True)
df2_1_lyst.columns
#для кожного ID господарства рахуємо кількість людей
#перевірка пустих значень
df2_1_lyst[df2_1_lyst['Males count'].isna()]
Out[22]:
| Кто заполнял базу | List ID | Link | ID Страница | ID Домохозяйствао | Губерния | Уезд | Волость | Село/деревня | Переписной участок | ... | Чем крыто | Males count | Females count | Постоянно живущаго М | Постоянно живущаго Ж | Некрестьянсокго сословия М | Некрестьянского сословия Ж | Приписанного здесь М | Приписанного здесь Ж | Подпись счетчика | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 216 | Podhorna | F.132.Op.1.Spr.1343 | NaN | 455_456 | 217 | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
1 rows × 25 columns
In [23]:
#перевірка цього господарства в іншій таблиці з людьми
df2_1_people[df2_1_people['ID Домохозяйствао'] == 217]
Out[23]:
| Кто заполнял базу | ID строки в базе | List ID | Link | ID Домохозяйствао | ID жилец | ФИО | Sex | Глава хозяйства и глава семьи | Age | ... | Здесь ли обыкновенно проживает | Отметка об отсуствии | Вероисповедание | Родной язык | Умеет ли читать | Обучение | Профессия главное | Профессия вспомогательное | Положение по воинской повинности | Примітки | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1281 | Podhorna | 1282.0 | F.132.Op.1.Spr.1343. Арк.455-456 | https://www.familysearch.org/ark:/61903/3:1:3Q... | 217.0 | 1.0 | Vorobej Evdokim Andreev | m | husband | 65 | ... | 1 | NaN | orthodox | ukr | 0.0 | NaN | farmer | NaN | NaN | NaN |
| 1282 | Podhorna | 1283.0 | F.132.Op.1.Spr.1343. Арк.455-456 | https://www.familysearch.org/ark:/61903/3:1:3Q... | 217.0 | 2.0 | Vorobej Marfa Aleksandrova | f | wife | 65 | ... | 1 | NaN | orthodox | ukr | 0.0 | NaN | farmer with husband | NaN | NaN | NaN |
| 1283 | Podhorna | 1284.0 | F.132.Op.1.Spr.1343. Арк.455-456 | https://www.familysearch.org/ark:/61903/3:1:3Q... | 217.0 | 3.0 | Vorobej Grigorij Evdokimov | m | son | 22 | ... | 1 | NaN | orthodox | ukr | 0.0 | NaN | farmer with father | NaN | NaN | NaN |
| 1284 | Podhorna | 1285.0 | F.132.Op.1.Spr.1343. Арк.455-456 | https://www.familysearch.org/ark:/61903/3:1:3Q... | 217.0 | 4.0 | Vorobej Elisaveta Evdokimova | f | daughter | 19 | ... | 1 | NaN | orthodox | ukr | 0.0 | NaN | farmer with father | NaN | NaN | NaN |
| 1285 | Podhorna | 1286.0 | F.132.Op.1.Spr.1343. Арк.455-456 | https://www.familysearch.org/ark:/61903/3:1:3Q... | 217.0 | 5.0 | Vorobej Anna Evdokimova | f | daughter | 27 | ... | 1 | NaN | orthodox | ukr | 0.0 | NaN | farmer with father | NaN | NaN | NaN |
| 1286 | Podhorna | 1287.0 | F.132.Op.1.Spr.1343. Арк.455-456 | https://www.familysearch.org/ark:/61903/3:1:3Q... | 217.0 | 6.0 | Vorobej Agafiya doch' devicy | f | granddaughter/extramarital daughter of #5 | 1 | ... | 1 | NaN | orthodox | ukr | 0.0 | NaN | with mother | NaN | NaN | NaN |
6 rows × 25 columns
In [24]:
df2_1_lyst.loc[216, 'Males count'] = 2
df2_1_lyst.loc[216, 'Females count'] = 4
#вручну вводимо кількість
In [25]:
df2_1_lyst['People count'] = df2_1_lyst['Males count'] + df2_1_lyst['Females count']
df2_1_lyst['People count'].describe()
Out[25]:
count 261.000000 mean 5.455939 std 2.499803 min 1.000000 25% 4.000000 50% 5.000000 75% 7.000000 max 18.000000 Name: People count, dtype: float64
In [26]:
df2_1_lyst['People count'].quantile(0.99)
Out[26]:
np.float64(12.0)
In [27]:
#буде доречно логарифмувати, оскільки родин із більш, ніж 12 людьми 1%
df2_1_lyst['log_people'] = np.log(df2_1_lyst['People count'])
In [28]:
#датасет 2
#додаємо стовпець із інформацією про розміри сімей
mask = df2_2_base_processed['ID домогосподарство'].notna()
filled_idxs = df2_2_base_processed.index[mask].tolist() + [len(df2_2_base_processed)]
df2_2_base_processed['People count'] = np.nan
for i in range(len(filled_idxs) - 1):
start = filled_idxs[i]
end = filled_idxs[i + 1]
df2_2_base_processed.at[start, 'People count'] = end - start
In [29]:
df2_2_base_processed['People count'].describe()
Out[29]:
count 105.000000 mean 15.209524 std 11.488870 min 2.000000 25% 9.000000 50% 12.000000 75% 18.000000 max 71.000000 Name: People count, dtype: float64
In [30]:
#це також логарифмуємо
df2_2_base_processed['log_people'] = np.log(df2_2_base_processed['People count'])
In [31]:
people_count_1 = df2_1_lyst['log_people'].dropna()
people_count_2 = df2_2_base_processed['log_people'].dropna()
plt.figure(figsize=(10, 6))
plt.hist(people_count_2, bins=30, alpha=0.7, label='1778 р.', color='red')
plt.hist(people_count_1, bins=30, alpha=0.7, label='1897 р.', color='blue')
plt.xlabel('Кількість людей (log)')
plt.ylabel('Частота')
plt.title('Гістограма розмірів сімей')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
In [32]:
#тепер можна перейти до структур родин
#всі дані потрібно об'єднати в 1 датафрейм та залишити лише необхідне
dataset1 = {
'козаки': pd.read_csv('2.1.1_Структура родини Дунаєць_1897.xlsx - Cossacks.csv'),
'духовництво': pd.read_csv('2.1.1_Структура родини Дунаєць_1897.xlsx - Dukhovnogo.csv'),
'селянські власники': pd.read_csv('2.1.1_Структура родини Дунаєць_1897.xlsx - Peasant-owner(1).csv'),
'шляхта': pd.read_csv('2.1.1_Структура родини Дунаєць_1897.xlsx - Nobles+imenuyushchijsya dvoryan.csv'),
}
dataset2 = {
'козаки': pd.read_csv('2.2.2_Структура родини Дунаєць_1778.xlsx - Военные.csv'),
'духовництво': pd.read_csv('2.2.2_Структура родини Дунаєць_1778.xlsx - Духовные(1).csv'),
'селянські власники': pd.read_csv('2.2.2_Структура родини Дунаєць_1778.xlsx - Посполитые+бездворные(1).csv'),
}
classes = ['козаки', 'духовництво', 'селянські власники', 'шляхта']
all_data = []
In [33]:
for class_name in classes:
if class_name in dataset1:
df1 = dataset1[class_name].copy()
df1['dataset'] = '1897'
df1['class'] = class_name
all_data.append(df1)
if dataset2.get(class_name) is not None:
df2 = dataset2[class_name].copy()
df2['dataset'] = '1778'
df2['class'] = class_name
all_data.append(df2)
df = pd.concat(all_data, ignore_index=True)
In [34]:
df = df.drop(['Кількість', '%', '% кожного класу', 'Клас'], axis=1)
In [35]:
#тепер потрібно підіграти кількість населення у категоріях у рядок із кожною категорією
#класи я не буду враховувати у візуалізації, оскільки тоді вона буде перевантаженою
category_indices = df.index[df['Категорія'].notna()].tolist()
for i, idx in enumerate(category_indices):
next_idx = category_indices[i+1] if i+1 < len(category_indices) else len(df)
sub_df = df.loc[idx+1:next_idx-1, 'Усього по категоріях']
value = sub_df[sub_df.notna()].first_valid_index()
if value is not None:
df.at[idx, 'Усього по категоріях'] = df.at[value, 'Усього по категоріях']
df = df[df['Категорія'].notna()].reset_index(drop=True)
df_filtered = df[df['Категорія'].str.lower().str.strip() != 'усього'].copy()
In [36]:
grouped = df_filtered.groupby(['Категорія', 'dataset', 'class'])['Усього по категоріях'].sum().reset_index()
#створюємо таблицю де розкладаємо кількість осіб 'Усього по категоріях' по категоріях, щоб кожен клас був окремим стовпцем
pivot = grouped.pivot_table(index=['Категорія', 'dataset'],
columns='class',
values='Усього по категоріях',
fill_value=0).reset_index()
pivot = pivot.sort_values(['Категорія', 'dataset'])
numeric_columns = pivot.columns[2:]
for col in numeric_columns:
pivot[col] = pd.to_numeric(pivot[col], errors='coerce').fillna(0)
categories = pivot['Категорія'].dropna().unique()
years = pivot['dataset'].dropna().unique()
#будуємо графік
fig, ax = plt.subplots(figsize=(16, 10))
n_categories = len(categories)
n_years = len(years)
bar_width = 0.35
group_width = bar_width * n_years
group_spacing = 0.3
colors = ['pink', 'lightblue', 'wheat', 'violet']
#правильне розташування
x_positions = np.arange(n_categories) * (group_width + group_spacing)
#малює стовпчикові діаграми для кожного року, посуваючи їх по осі x та stacked bar chart за категоріями
for i, year in enumerate(years):
year_data = pivot[pivot['dataset'] == year]
x_pos = x_positions + i * bar_width
bottom = np.zeros(len(year_data))
for j, class_col in enumerate(numeric_columns):
values = year_data[class_col].values
ax.bar(x_pos, values, bar_width, bottom=bottom,
color=colors[j % len(colors)], alpha=0.8,
label=class_col if i == 0 else "")
bottom += values
ax.set_ylabel('Кількість')
ax.set_title('Сімейні структури у 1897 та 1778 роках')
y_max = pivot[numeric_columns].sum(axis=1).max()
ax.set_ylim(0, y_max * 1.1)
ax.set_xticks(x_positions + bar_width / 2)
ax.set_xticklabels(categories)
max_value = pivot[numeric_columns].sum(axis=1).max()
for i, year in enumerate(years):
for j, category in enumerate(categories):
ax.text(x_positions[j] + i * bar_width, -5,
str(year), ha='center', va='top', fontsize=9)
handles, labels = ax.get_legend_handles_labels()
by_label = dict(zip(labels, handles))
ax.legend(by_label.values(), by_label.keys(), title='Клас', loc='upper left')
plt.tight_layout()
plt.show()
In [37]:
'''Із отриманих візуалізацій можна зробити такі висновки:
-за 100 років розміри сімей відчутно зменшилися, що повпливало і на структури сімей
-у мультифокальних сім'ях збільшилась частка козаків (військових) та з'явилась шляхта
-кількість нуклеарних родин стрімко зросла, але частка духовенства у ній зменшилась
-кількість розширених родин також зросла
-кількість самотніх осіб незначно збільшилась
'''
Out[37]:
"Із отриманих візуалізацій можна зробити такі висновки:\n-за 100 років розміри сімей відчутно зменшилися, що повпливало і на структури сімей\n-у мультифокальних сім'ях збільшилась частка козаків (військових) та з'явилась шляхта\n-кількість нуклеарних родин стрімко зросла, але частка духовенства у ній зменшилась\n-кількість розширених родин також зросла\n-кількість самотніх осіб незначно збільшилась\n"
In [38]:
#Які прізвища чи родини з обох баз можна повʼязати
#1 датафрейм
df1 = df2_1_people
#почнемо із розділення колонки 'ФИО' на окремі елементи
split_pib = df1['ФИО'].str.strip().str.split(' ', n=2, expand=True)
split_pib.columns = ['Surname', 'Name', 'Patronymic']
df1 = df1.join(split_pib)
df1[['Surname', 'Name', 'Patronymic']] = df1[['Surname', 'Name', 'Patronymic']].apply(lambda col: col.str.lower().str.strip())
In [39]:
df1[df1['Surname'].isna()]
#аналогічно з ситуацією з розміром родини і віком, перевіряємо в іншому датасеті це господарство
Out[39]:
| Кто заполнял базу | ID строки в базе | List ID | Link | ID Домохозяйствао | ID жилец | ФИО | Sex | Глава хозяйства и глава семьи | Age | ... | Родной язык | Умеет ли читать | Обучение | Профессия главное | Профессия вспомогательное | Положение по воинской повинности | Примітки | Surname | Name | Patronymic | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 987 | Podhorna | 988.0 | F.132.Op.1.Spr.1343. Арк.355-356 | NaN | 169.0 | NaN | NaN | m | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 988 | Podhorna | 989.0 | F.132.Op.1.Spr.1343. Арк.355-356 | NaN | 169.0 | NaN | NaN | f | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 989 | Podhorna | 990.0 | F.132.Op.1.Spr.1343. Арк.355-356 | NaN | 169.0 | NaN | NaN | f | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
3 rows × 28 columns
In [40]:
df1_merged = pd.merge(df1, df2_1_lyst, on='ID Домохозяйствао')
df1_merged.loc[df1_merged['ФИО'].isna(), 'Хозяин']
Out[40]:
987 Ivan Samuilov Cygikal 988 Ivan Samuilov Cygikal 989 Ivan Samuilov Cygikal Name: Хозяин, dtype: object
In [41]:
#припустимо, що у всіх в родині прізвище Cygikal
df1.loc[[987, 988, 989], 'Surname'] = 'cygikal'
In [42]:
#2 датафрейм
df2 =df2_2_base_processed
In [43]:
df2['Прізвище'] = df2['Прізвище'].replace('', pd.NA)
df2['Прізвище'] = df2['Прізвище'].ffill()
#заповнила пусті клітинки прізвищами господарів
In [44]:
def transliterate_to_latin(text):
return translit(text, 'ru', reversed=True).lower().strip()
df2['Surname_latin'] = df2['Прізвище'].apply(transliterate_to_latin)
In [45]:
#для виявлення схожих прізвищ використаємо rapidfuzz
surnames_1897 = df1['Surname'].dropna().unique()
surnames_1778 = df2['Surname_latin'].dropna().unique()
threshold = 80
#це доволі оптимальне значення мінімальної схожості для незначних змін у прізвищах
matches = []
for surname in surnames_1897:
match = process.extractOne(surname, surnames_1778, scorer=fuzz.ratio)
if match and match[1] >= threshold:
matches.append({'surname_1897': surname, 'surname_1778': match[0], 'similarity': match[1]})
matches_df = pd.DataFrame(matches)
In [46]:
heatmap = alt.Chart(matches_df).mark_rect().encode(
x=alt.X('surname_1897:N', title='Прізвища з 1897', sort=alt.EncodingSortField(field="surname_1897", order='ascending')),
y=alt.Y('surname_1778:N', title='Прізвища з 1778', sort=alt.EncodingSortField(field="surname_1778", order='ascending')),
color=alt.Color('similarity:Q', scale=alt.Scale(scheme='blues'), legend=alt.Legend(title='Схожість')),
tooltip=['surname_1897', 'surname_1778', 'similarity']
).properties(
title='Теплова карта схожості прізвищ 1897 та 1778 років',
width=800,
height=800
)
heatmap
Out[46]:
In [47]:
'''Ця heatmap дозволяє побачити схожість наявних прізвищ і проаналізувати зв'язок між ними
-найтемніші відтінки свідчать про однаковість прізвищ, тому можна стверджувати, що вони належали одній родині
-такі призвища, як, наприклад, Науменко і Науменкова можна пов'язати і аргументувати зміну закінчення історичним контекстом - русифікацією прізвищ
-а, наприклад, poznyak та poznjak відмінними трансітераціями
-інші прізвища можна пояснити помилками в записі'''
Out[47]:
"Ця heatmap дозволяє побачити схожість наявних прізвищ і проаналізувати зв'язок між ними\n-найтемніші відтінки свідчать про однаковість прізвищ, тому можна стверджувати, що вони належали одній родині\n-такі призвища, як, наприклад, Науменко і Науменкова можна пов'язати і аргументувати зміну закінчення історичним контекстом - русифікацією прізвищ\n-а, наприклад, poznyak та poznjak відмінними трансітераціями\n-інші прізвища можна пояснити помилками в записі"